/****************************************************************************
 Copyright (C) 2013 Henry van Merode. All rights reserved.
 Copyright (c) 2015-2016 Chukong Technologies Inc.
 Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.

 https://axmolengine.github.io/

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 ****************************************************************************/

#include "Particle3D/PU/PUParticleSystem3D.h"
#include "Particle3D/PU/PUEmitter.h"
#include "Particle3D/PU/PUEmitterManager.h"
#include "Particle3D/PU/PUAffector.h"
#include "Particle3D/PU/PUAffectorManager.h"
#include "Particle3D/Particle3DRender.h"
#include "Particle3D/PU/PUScriptCompiler.h"
#include "Particle3D/PU/PUMaterialManager.h"
#include "Particle3D/PU/PUTranslateManager.h"
#include "Particle3D/PU/PUListener.h"
#include "Particle3D/PU/PUObserver.h"
#include "Particle3D/PU/PUObserverManager.h"
#include "Particle3D/PU/PUBehaviour.h"
#include "platform/FileUtils.h"

NS_AX_BEGIN

float PUParticle3D::DEFAULT_TTL  = 10.0f;
float PUParticle3D::DEFAULT_MASS = 1.0f;

float PUParticle3D::calculateVelocity() const
{
    if (originalScaledDirectionLength != 0)
    {
        return originalVelocity * (direction.length() / originalScaledDirectionLength);
    }
    else
    {
        // Assume originalScaledDirectionLength to be 1.0 (unit vector)
        return originalVelocity * direction.length();
    }
}

void PUParticle3D::setOwnDimensions(float newWidth, float newHeight, float newDepth)
{
    ownDimensions = true;
    if (newWidth)
        width = newWidth;
    if (newHeight)
        height = newHeight;
    if (newDepth)
        depth = newDepth;
    calculateBoundingSphereRadius();
    // parentEmitter->getParentTechnique()->_notifyParticleResized();
}

void PUParticle3D::calculateBoundingSphereRadius()
{
    // radius = 0.5 * Math::Sqrt(width*width + height*height + depth*depth);
    radius = 0.5f * std::max(depth, std::max(width, height));  // approximation
}

void PUParticle3D::initForEmission()
{
    eventFlags   = 0;
    timeFraction = 0.0f;
    /*	Note, that this flag must only be set as soon as the particle is emitted. As soon as the particle has
        been moved once, the flag must be removed again.
    */
    addEventFlags(PUParticle3D::PEF_EMITTED);

    // Reset freeze flag
    freezed = false;

    if (!behaviours.empty())
    {
        for (auto&& it : behaviours)
        {
            it->initParticleForEmission(this);
        }
    }
}

void PUParticle3D::initForExpiration(float timeElapsed)
{
    if (!behaviours.empty())
    {
        for (auto&& it : behaviours)
        {
            it->initParticleForExpiration(this, timeElapsed);
        }
    }
}

void PUParticle3D::process(float timeElapsed)
{
    timeFraction = (totalTimeToLive - timeToLive) / totalTimeToLive;

    if (!behaviours.empty())
    {
        for (auto&& it : behaviours)
        {
            it->updateBehaviour(this, timeElapsed);
        }
    }
}

PUParticle3D::PUParticle3D()
    : particleEntityPtr(nullptr)
    , parentEmitter(nullptr)
    , visualData(nullptr)
    , particleType(PT_VISUAL)
    , originalDirectionLength(0.0f)
    , originalVelocity(0.0f)
    , originalScaledDirectionLength(0.0f)
    , rotationAxis(Vec3::UNIT_Z)
    ,
    // color(Vec4::ONE),
    originalColor(Vec4::ONE)
    ,
    // zRotation(0.0f),
    zRotationSpeed(0.0f)
    , rotationSpeed(0.0f)
    , radius(0.87f)
    , ownDimensions(false)
    , eventFlags(0)
    , freezed(false)
    , timeToLive(DEFAULT_TTL)
    , totalTimeToLive(DEFAULT_TTL)
    , timeFraction(0.0f)
    , mass(DEFAULT_MASS)
    , textureAnimationTimeStep(0.1f)
    , textureAnimationTimeStepCount(0.0f)
    , textureCoordsCurrent(0)
    , textureAnimationDirectionUp(true)
    , depthInView(0.0f)
    , zRotation(0.0f)
// widthInWorld(width),
// heightInWorld(height),
// depthInWorld(depth)
{}

PUParticle3D::~PUParticle3D()
{
    for (auto&& it : behaviours)
    {
        it->release();
    }

    // AX_SAFE_RELEASE(particleEntityPtr);
}

void PUParticle3D::copyBehaviours(const ParticleBehaviourList& list)
{
    for (auto&& it : list)
    {
        auto behaviour = it->clone();
        behaviour->retain();
        behaviours.emplace_back(behaviour);
    }
}

//-----------------------------------------------------------------------

const float PUParticleSystem3D::DEFAULT_WIDTH                        = 50;
const float PUParticleSystem3D::DEFAULT_HEIGHT                       = 50;
const float PUParticleSystem3D::DEFAULT_DEPTH                        = 50;
const unsigned int PUParticleSystem3D::DEFAULT_PARTICLE_QUOTA        = 500;
const unsigned int PUParticleSystem3D::DEFAULT_EMITTED_EMITTER_QUOTA = 50;
const unsigned int PUParticleSystem3D::DEFAULT_EMITTED_SYSTEM_QUOTA  = 10;
const float PUParticleSystem3D::DEFAULT_MAX_VELOCITY                 = 9999.0f;

PUParticleSystem3D::PUParticleSystem3D()
    : _emittedEmitterQuota(DEFAULT_EMITTED_EMITTER_QUOTA)
    , _emittedSystemQuota(DEFAULT_EMITTED_SYSTEM_QUOTA)
    , _prepared(false)
    , _poolPrepared(false)
    , _particleSystemScaleVelocity(1.0f)
    , _timeElapsedSinceStart(0.0f)
    , _defaultWidth(DEFAULT_WIDTH)
    , _defaultHeight(DEFAULT_HEIGHT)
    , _defaultDepth(DEFAULT_DEPTH)
    , _maxVelocity(DEFAULT_MAX_VELOCITY)
    , _maxVelocitySet(false)
    , _isMarkedForEmission(false)
    , _parentParticleSystem(nullptr)
{
    _particleQuota = DEFAULT_PARTICLE_QUOTA;
}
PUParticleSystem3D::~PUParticleSystem3D()
{
    stopParticleSystem();
    unPrepared();

    _particlePool.removeAllDatas();

    for (auto&& iter : _emittedEmitterParticlePool)
    {
        auto pool       = iter.second;
        auto lockedList = pool.getUnActiveDataList();
        for (auto&& iter2 : lockedList)
        {
            static_cast<PUParticle3D*>(iter2)->particleEntityPtr->release();
        }
        iter.second.removeAllDatas();
    }

    for (auto&& iter : _emittedSystemParticlePool)
    {
        auto pool       = iter.second;
        auto lockedList = pool.getUnActiveDataList();
        for (auto&& iter2 : lockedList)
        {
            static_cast<PUParticle3D*>(iter2)->particleEntityPtr->release();
        }
        iter.second.removeAllDatas();
    }

    // release all emitters
    for (auto&& it : _emitters)
    {
        it->release();
    }
    _emitters.clear();

    for (auto&& it : _observers)
    {
        it->release();
    }

    for (auto&& it : _behaviourTemplates)
    {
        it->release();
    }

    _observers.clear();
}

PUParticleSystem3D* PUParticleSystem3D::create()
{
    auto pups = new PUParticleSystem3D();
    pups->autorelease();
    return pups;
}

PUParticleSystem3D* PUParticleSystem3D::create(std::string_view filePath, std::string_view materialPath)
{
    PUParticleSystem3D* ret = new PUParticleSystem3D();
    if (ret->initWithFilePathAndMaterialPath(filePath, materialPath))
    {
        ret->autorelease();
        return ret;
    }
    else
    {
        AX_SAFE_DELETE(ret);
        return nullptr;
    }
}

PUParticleSystem3D* PUParticleSystem3D::create(std::string_view filePath)
{
    PUParticleSystem3D* ret = new PUParticleSystem3D();
    if (ret->initWithFilePath(filePath))
    {
        ret->autorelease();
        return ret;
    }
    else
    {
        AX_SAFE_DELETE(ret);
        return nullptr;
    }
}

bool PUParticleSystem3D::initWithFilePath(std::string_view filePath)
{
    std::string fullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
    convertToUnixStylePath(fullPath);
    std::string::size_type pos = fullPath.find_last_of('/');
    std::string materialFolder = "materials";
    if (pos != std::string::npos)
    {
        std::string temp = fullPath.substr(0, pos);
        pos              = temp.find_last_of('/');
        if (pos != std::string::npos)
        {
            materialFolder = temp.substr(0, pos + 1) + materialFolder;
        }
    }
    static std::vector<std::string> loadedFolder;
    if (std::find(loadedFolder.begin(), loadedFolder.end(), materialFolder) == loadedFolder.end())
    {
        PUMaterialCache::Instance()->loadMaterialsFromSearchPaths(materialFolder);
        loadedFolder.emplace_back(materialFolder);
    }

    if (!initSystem(fullPath))
    {
        return false;
    }
    return true;
}

bool PUParticleSystem3D::initWithFilePathAndMaterialPath(std::string_view filePath, std::string_view materialPath)
{
    std::string matfullPath = FileUtils::getInstance()->fullPathForFilename(materialPath);
    convertToUnixStylePath(matfullPath);
    PUMaterialCache::Instance()->loadMaterials(matfullPath);
    std::string fullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
    convertToUnixStylePath(fullPath);
    if (!initSystem(fullPath))
    {
        return false;
    }
    return true;
}

void PUParticleSystem3D::startParticleSystem()
{
    stopParticleSystem();

    if (_state != State::RUNNING)
    {
        forceStopParticleSystem();
        if (_render)
            _render->notifyStart();

        for (auto&& it : _observers)
        {
            it->notifyStart();
        }

        for (auto&& it : _emitters)
        {
            auto emitter = static_cast<PUEmitter*>(it);
            emitter->notifyStart();
        }

        for (auto&& it : _affectors)
        {
            auto affector = static_cast<PUAffector*>(it);
            affector->notifyStart();
        }

        scheduleUpdate();
        _state = State::RUNNING;
    }

    for (auto&& iter : _children)
    {
        PUParticleSystem3D* system = dynamic_cast<PUParticleSystem3D*>(iter);
        if (system)
        {
            system->_parentParticleSystem = this;
            system->startParticleSystem();
        }
    }
}

void PUParticleSystem3D::stopParticleSystem()
{
    if (_state != State::STOP)
    {
        _state = State::STOP;
    }

    for (auto&& iter : _children)
    {
        PUParticleSystem3D* system = dynamic_cast<PUParticleSystem3D*>(iter);
        if (system)
            system->stopParticleSystem();
    }
}

void PUParticleSystem3D::pauseParticleSystem()
{
    if (_state == State::RUNNING)
    {
        // if (_emitter)
        //{
        //     auto emitter = static_cast<PUEmitter*>(_emitter);
        //     emitter->notifyPause();
        // }

        for (auto&& it : _emitters)
        {
            auto emitter = static_cast<PUEmitter*>(it);
            emitter->notifyPause();
        }

        for (auto&& it : _affectors)
        {
            auto affector = static_cast<PUAffector*>(it);
            affector->notifyPause();
        }

        _state = State::PAUSE;
    }

    for (auto&& iter : _children)
    {
        PUParticleSystem3D* system = dynamic_cast<PUParticleSystem3D*>(iter);
        if (system)
            system->pauseParticleSystem();
    }
}

void PUParticleSystem3D::resumeParticleSystem()
{
    if (_state == State::PAUSE)
    {
        // if (_emitter)
        //{
        //     auto emitter = static_cast<PUEmitter*>(_emitter);
        //     emitter->notifyResume();
        // }

        for (auto&& it : _emitters)
        {
            auto emitter = static_cast<PUEmitter*>(it);
            emitter->notifyResume();
        }

        for (auto&& it : _affectors)
        {
            auto affector = static_cast<PUAffector*>(it);
            affector->notifyResume();
        }

        _state = State::RUNNING;
    }

    for (auto&& iter : _children)
    {
        PUParticleSystem3D* system = dynamic_cast<PUParticleSystem3D*>(iter);
        if (system)
            system->resumeParticleSystem();
    }
}

void PUParticleSystem3D::update(float delta)
{
    if (!_isEnabled || _isMarkedForEmission)
        return;
    if (_state != State::RUNNING)
    {
        if (_state == State::PAUSE)
            return;
        else if (_state == State::STOP && getAliveParticleCount() <= 0)
        {
            forceStopParticleSystem();
            return;
        }
    }

    forceUpdate(delta);
}

void PUParticleSystem3D::forceUpdate(float delta)
{
    if (!_emitters.empty())
        calulateRotationOffset();

    prepared();

    Vec3 currentPos     = getDerivedPosition();
    _latestPositionDiff = currentPos - _latestPosition;
    _latestPosition     = currentPos;
    _latestOrientation  = getDerivedOrientation();

    if (!_emitters.empty())
    {
        emitParticles(delta);
        preUpdator(delta);
        updator(delta);
        postUpdator(delta);
    }

    _timeElapsedSinceStart += delta;
}

float PUParticleSystem3D::getParticleSystemScaleVelocity() const
{
    return _particleSystemScaleVelocity;
}

void PUParticleSystem3D::rotationOffset(Vec3& pos)
{
    Mat4 rotMat;
    Mat4::createRotation(_rotationOffset, &rotMat);
    pos = _rotationCentre + rotMat * (pos - _rotationCentre);
}

void PUParticleSystem3D::prepared()
{
    if (!_prepared)
    {
        // if (_emitter && _emitter->isEnabled())
        //{
        //     auto emitter = static_cast<PUEmitter*>(_emitter);
        //     emitter->prepare();
        // }

        if (_render)
            static_cast<PURender*>(_render)->prepare();

        for (auto&& it : _behaviourTemplates)
        {
            it->prepare();
        }

        for (auto&& it : _emitters)
        {
            // if (it->isEnabled())
            (static_cast<PUEmitter*>(it))->prepare();
        }

        for (auto&& it : _affectors)
        {
            // if (it->isEnabled())
            (static_cast<PUAffector*>(it))->prepare();
        }

        if (!_poolPrepared)
        {
            for (auto&& it : _emitters)
            {
                // if (it->isEnabled())
                PUEmitter* emitter = static_cast<PUEmitter*>(it);
                if (emitter->getEmitsType() == PUParticle3D::PT_EMITTER)
                {
                    PUEmitter* emitted = static_cast<PUEmitter*>(emitter->getEmitsEntityPtr());
                    for (unsigned int i = 0; i < _emittedEmitterQuota; ++i)
                    {
                        auto p               = new PUParticle3D();
                        p->particleType      = PUParticle3D::PT_EMITTER;
                        p->particleEntityPtr = emitted->clone();
                        p->particleEntityPtr->retain();
                        p->copyBehaviours(_behaviourTemplates);
                        _emittedEmitterParticlePool[emitted->getName()].addData(p);
                    }
                }
                else if (emitter->getEmitsType() == PUParticle3D::PT_TECHNIQUE)
                {
                    PUParticleSystem3D* emitted = static_cast<PUParticleSystem3D*>(emitter->getEmitsEntityPtr());
                    for (unsigned int i = 0; i < _emittedSystemQuota; ++i)
                    {
                        PUParticleSystem3D* clonePS = emitted->clone();
                        auto p                      = new PUParticle3D();
                        p->particleType             = PUParticle3D::PT_TECHNIQUE;
                        p->particleEntityPtr        = clonePS;
                        p->particleEntityPtr->retain();
                        p->copyBehaviours(_behaviourTemplates);
                        _emittedSystemParticlePool[emitted->getName()].addData(p);
                        clonePS->prepared();
                    }
                    // emitted->stopParticle();
                }
            }

            for (unsigned int i = 0; i < _particleQuota; ++i)
            {
                auto p = new PUParticle3D();
                p->copyBehaviours(_behaviourTemplates);
                _particlePool.addData(p);
            }
            _poolPrepared = true;
        }

        _prepared              = true;
        _timeElapsedSinceStart = 0.0f;
        _latestPosition        = getDerivedPosition();  // V1.3.1
        if (_parentParticleSystem)
        {
            _particleSystemScaleVelocity = _parentParticleSystem->getParticleSystemScaleVelocity();
        }
    }

    if (!_emitters.empty())
        notifyRescaled(getDerivedScale());
}

void PUParticleSystem3D::unPrepared()
{
    // if (_emitter && _emitter->isEnabled())
    //{
    //      auto emitter = static_cast<PUEmitter*>(_emitter);
    //     emitter->unPrepare();
    // }
    if (_prepared)
    {
        if (_render)
            static_cast<PURender*>(_render)->unPrepare();

        for (auto&& it : _behaviourTemplates)
        {
            it->unPrepare();
        }

        for (auto&& it : _emitters)
        {
            if (it->isEnabled())
                (static_cast<PUEmitter*>(it))->unPrepare();
        }

        for (auto&& it : _affectors)
        {
            if (it->isEnabled())
                (static_cast<PUAffector*>(it))->unPrepare();
        }

        _particlePool.lockAllDatas();
        for (auto&& iter : _emittedEmitterParticlePool)
        {
            PUParticle3D* particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getFirst());
            while (particle)
            {
                static_cast<PUEmitter*>(particle->particleEntityPtr)->unPrepare();
                particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getNext());
            }
            const_cast<ParticlePool&>(iter.second).lockAllDatas();
        }

        for (auto&& iter : _emittedSystemParticlePool)
        {
            PUParticle3D* particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getFirst());
            while (particle)
            {
                static_cast<PUParticleSystem3D*>(particle->particleEntityPtr)->unPrepared();
                particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getNext());
            }
            const_cast<ParticlePool&>(iter.second).lockAllDatas();
        }
        _prepared = false;
    }
}

void PUParticleSystem3D::preUpdator(float elapsedTime)
{
    // if (_emitter && _emitter->isEnabled())
    //{
    //      auto emitter = static_cast<PUEmitter*>(_emitter);
    //     emitter->preUpdateEmitter(elapsedTime);
    // }
    // bool hasNoEmitted = true;
    for (auto&& it : _emitters)
    {
        if (!it->isEmitterDone())
        {
            (static_cast<PUEmitter*>(it))->preUpdateEmitter(elapsedTime);
            // hasNoEmitted = false;
        }
    }

    // if (hasNoEmitted){
    //     if (_particlePool.getActiveParticleList().empty())
    //         stopParticle();
    // }

    for (auto&& it : _affectors)
    {
        if (it->isEnabled())
        {
            (static_cast<PUAffector*>(it))->preUpdateAffector(elapsedTime);
        }
    }

    for (auto&& it : _observers)
    {
        if (it->isEnabled())
        {
            it->preUpdateObserver(elapsedTime);
        }
    }

    for (auto&& iter : _emittedEmitterParticlePool)
    {
        PUParticle3D* particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getFirst());
        while (particle)
        {
            static_cast<PUEmitter*>(particle->particleEntityPtr)->preUpdateEmitter(elapsedTime);
            particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getNext());
        }
    }

    for (auto&& iter : _emittedSystemParticlePool)
    {
        PUParticle3D* particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getFirst());
        while (particle)
        {
            static_cast<PUParticleSystem3D*>(particle->particleEntityPtr)->preUpdator(elapsedTime);
            particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getNext());
        }
    }
}

void PUParticleSystem3D::updator(float elapsedTime)
{
    bool firstActiveParticle = true;
    bool firstParticle       = true;
    processParticle(_particlePool, firstActiveParticle, firstParticle, elapsedTime);

    for (auto&& iter : _emittedEmitterParticlePool)
    {
        processParticle(const_cast<ParticlePool&>(iter.second), firstActiveParticle, firstParticle, elapsedTime);
    }

    for (auto&& iter : _emittedSystemParticlePool)
    {
        processParticle(const_cast<ParticlePool&>(iter.second), firstActiveParticle, firstParticle, elapsedTime);
    }
}

void PUParticleSystem3D::postUpdator(float elapsedTime)
{
    // if (_emitter && _emitter->isEnabled())
    //{
    //     auto emitter = static_cast<PUEmitter*>(_emitter);
    //     emitter->postUpdateEmitter(elapsedTime);
    // }

    for (auto&& it : _emitters)
    {
        if (it->isEnabled())
        {
            (static_cast<PUEmitter*>(it))->postUpdateEmitter(elapsedTime);
        }
    }

    for (auto&& it : _affectors)
    {
        if (it->isEnabled())
        {
            auto affector = static_cast<PUAffector*>(it);
            affector->postUpdateAffector(elapsedTime);
        }
    }

    for (auto&& it : _observers)
    {
        if (it->isEnabled())
        {
            it->postUpdateObserver(elapsedTime);
        }
    }

    for (auto&& iter : _emittedEmitterParticlePool)
    {
        PUParticle3D* particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getFirst());
        while (particle)
        {
            static_cast<PUEmitter*>(particle->particleEntityPtr)->postUpdateEmitter(elapsedTime);
            particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getNext());
        }
    }

    for (auto&& iter : _emittedSystemParticlePool)
    {
        PUParticle3D* particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getFirst());
        while (particle)
        {
            static_cast<PUParticleSystem3D*>(particle->particleEntityPtr)->postUpdator(elapsedTime);
            particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getNext());
        }
    }
}

void PUParticleSystem3D::emitParticles(float elapsedTime)
{
    // Vec3 scale = getDerivedScale();
    for (auto&& iter : _emitters)
    {
        // if (!_emitter) return;
        // auto emitter = static_cast<PUEmitter*>(_emitter);
        auto emitter = iter;
        // emitter->notifyRescaled(scale);
        if (!iter->isMarkedForEmission())
        {
            unsigned short requested = emitter->calculateRequestedParticles(elapsedTime);
            executeEmitParticles(emitter, requested, elapsedTime);
        }
    }
}

float PUParticleSystem3D::getDefaultWidth() const
{
    return _defaultWidth;
}

void PUParticleSystem3D::setDefaultWidth(const float width)
{
    _defaultWidth = width;
}

float PUParticleSystem3D::getDefaultHeight() const
{
    return _defaultHeight;
}

void PUParticleSystem3D::setDefaultHeight(const float height)
{
    _defaultHeight = height;
}

float PUParticleSystem3D::getDefaultDepth() const
{
    return _defaultDepth;
}

void PUParticleSystem3D::setDefaultDepth(const float depth)
{
    _defaultDepth = depth;
}

bool PUParticleSystem3D::isExpired(PUParticle3D* particle, float timeElapsed)
{
    bool expired = particle->timeToLive < timeElapsed;
    if (expired)
    {
        // Set the flag to indicate that the particle has been expired
        particle->addEventFlags(PUParticle3D::PEF_EXPIRED);
    }

    return expired;
}

ax::Vec3 PUParticleSystem3D::getDerivedPosition()
{
    // if (_parentParticleSystem && _parentParticleSystem->isKeepLocal()) return Vec3::ZERO;
    // if (_keepLocal) return Vec3::ZERO;
    if (_isMarkedForEmission)
    {
        return Vec3(_position.x, _position.y, _positionZ);
    }
    else
    {
        Mat4 mat = getNodeToWorldTransform();
        return Vec3(mat.m[12], mat.m[13], mat.m[14]);
    }
}

ax::Quaternion PUParticleSystem3D::getDerivedOrientation()
{
    // if (_parentParticleSystem && _parentParticleSystem->isKeepLocal()) return Quaternion();
    // if (_keepLocal) return Quaternion();
    if (_isMarkedForEmission)
    {
        return getRotationQuat();
    }
    else
    {
        Quaternion q;
        Mat4 mat = getNodeToWorldTransform();
        mat.decompose(nullptr, &q, nullptr);
        return q;
    }
}

ax::Vec3 PUParticleSystem3D::getDerivedScale()
{
    // if (_parentParticleSystem && _parentParticleSystem->isKeepLocal()) return Vec3::ONE;
    // if (_keepLocal) return Vec3::ONE;
    if (_isMarkedForEmission)
    {
        return Vec3(_scaleX, _scaleY, _scaleZ);
    }
    else
    {
        Vec3 s;
        Mat4 mat = getNodeToWorldTransform();
        mat.decompose(&s, nullptr, nullptr);
        return s;
    }
}

float PUParticleSystem3D::getMaxVelocity() const
{
    return _maxVelocity;
}

void PUParticleSystem3D::setMaxVelocity(float maxVelocity)
{
    _maxVelocity    = maxVelocity;
    _maxVelocitySet = true;
}

bool PUParticleSystem3D::initSystem(std::string_view filePath)
{
    bool isFirstCompile = true;
    auto list           = PUScriptCompiler::Instance()->compile(filePath, isFirstCompile);
    if (list == nullptr || list->empty())
        return false;
    PUTranslateManager::Instance()->translateParticleSystem(this, list);
    // std::string  data = FileUtils::getInstance()->getStringFromFile(filePath);
    return true;
}

void PUParticleSystem3D::addEmitter(PUEmitter* emitter)
{
    if (emitter && std::find(_emitters.begin(), _emitters.end(), emitter) == _emitters.end())
    {
        emitter->_particleSystem = this;
        emitter->retain();
        _emitters.emplace_back(emitter);
    }
}

PUAffector* PUParticleSystem3D::getAffector(std::string_view name)
{
    for (auto&& iter : _affectors)
    {
        auto pa = static_cast<PUAffector*>(iter);
        if (pa->getName() == name)
            return pa;
    }

    return nullptr;
}

PUEmitter* PUParticleSystem3D::getEmitter(std::string_view name)
{
    for (auto&& iter : _emitters)
    {
        auto pe = static_cast<PUEmitter*>(iter);
        if (pe->getName() == name)
            return pe;
    }

    return nullptr;
}

void PUParticleSystem3D::addListener(PUListener* listener)
{
    auto iter = std::find(_listeners.begin(), _listeners.end(), listener);
    if (iter == _listeners.end())
    {
        _listeners.emplace_back(listener);
    }
}

void PUParticleSystem3D::removeListener(PUListener* listener)
{
    auto iter = std::find(_listeners.begin(), _listeners.end(), listener);
    if (iter != _listeners.end())
    {
        _listeners.erase(iter);
    }
}

void PUParticleSystem3D::forceEmission(PUEmitter* emitter, unsigned requested)
{
    // Fast rejection: Don't emit if the technique is not enabled
    if (!_isEnabled)
        return;

    executeEmitParticles(emitter, requested, 0);
}

void PUParticleSystem3D::executeEmitParticles(PUEmitter* emitter, unsigned requested, float elapsedTime)
{
    if (_state == State::STOP)
        return;
    if (emitter->getEmitsType() == PUParticle3D::PT_VISUAL)
    {
        emitParticles(_particlePool, emitter, requested, elapsedTime);
    }
    else if (emitter->getEmitsType() == PUParticle3D::PT_EMITTER)
    {
        emitParticles(_emittedEmitterParticlePool[emitter->getEmitsName()], emitter, requested, elapsedTime);
    }
    else if (emitter->getEmitsType() == PUParticle3D::PT_TECHNIQUE)
    {
        emitParticles(_emittedSystemParticlePool[emitter->getEmitsName()], emitter, requested, elapsedTime);
    }
}

void PUParticleSystem3D::emitParticles(ParticlePool& pool, PUEmitter* emitter, unsigned requested, float elapsedTime)
{
    Vec3 scale = getDerivedScale();
    Mat4 rotMat;
    Mat4::createRotation(getDerivedOrientation(), &rotMat);
    float timePoint = 0.0f;
    float timeInc   = elapsedTime / requested;
    for (unsigned short i = 0; i < requested; ++i)
    {
        PUParticle3D* particle = static_cast<PUParticle3D*>(pool.createData());
        if (!particle)
            return;

        particle->initForEmission();
        emitter->initParticleForEmission(particle);

        particle->direction = (rotMat * Vec3(particle->direction.x, particle->direction.y, particle->direction.z));
        particle->originalDirection = (rotMat * Vec3(particle->originalDirection.x, particle->originalDirection.y,
                                                     particle->originalDirection.z));

        for (auto&& it : _affectors)
        {
            if (it->isEnabled())
            {
                (static_cast<PUAffector*>(it))->initParticleForEmission(particle);
            }
        }

        initParticleForEmission(particle);

        particle->position.add(particle->direction.x * scale.x * _particleSystemScaleVelocity * timePoint,
                               particle->direction.y * scale.y * _particleSystemScaleVelocity * timePoint,
                               particle->direction.z * scale.z * _particleSystemScaleVelocity * timePoint);
        // Increment time fragment
        timePoint += timeInc;
    }
}

void PUParticleSystem3D::addObserver(PUObserver* observer)
{
    auto iter = std::find(_observers.begin(), _observers.end(), observer);
    if (iter == _observers.end())
    {
        observer->retain();
        observer->_particleSystem = this;
        _observers.emplace_back(observer);
    }
}

PUObserver* PUParticleSystem3D::getObserver(std::string_view name)
{
    for (auto&& iter : _observers)
    {
        auto po = static_cast<PUObserver*>(iter);
        if (po->getName() == name)
            return po;
    }

    return nullptr;
}

void PUParticleSystem3D::notifyRescaled(const Vec3& scl)
{
    if (_render)
        _render->notifyRescaled(scl);

    for (auto&& it : _emitters)
    {
        (static_cast<PUEmitter*>(it))->notifyRescaled(scl);
    }

    for (auto&& it : _affectors)
    {
        (static_cast<PUAffector*>(it))->notifyRescaled(scl);
    }

    for (auto&& it : _observers)
    {
        it->notifyRescaled(scl);
    }

    for (auto&& iter : _emittedEmitterParticlePool)
    {
        PUParticle3D* particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getFirst());
        while (particle)
        {
            static_cast<PUEmitter*>(particle->particleEntityPtr)->notifyRescaled(scl);
            particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getNext());
        }
    }

    for (auto&& iter : _emittedSystemParticlePool)
    {
        PUParticle3D* particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getFirst());
        while (particle)
        {
            static_cast<PUParticleSystem3D*>(particle->particleEntityPtr)->notifyRescaled(scl);
            particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getNext());
        }
    }
}

void PUParticleSystem3D::initParticleForExpiration(PUParticle3D* particle, float timeElapsed)
{
    if (particle->particleType == PUParticle3D::PT_EMITTER)
    {
        PUEmitter* emitter = static_cast<PUEmitter*>(particle->particleEntityPtr);
        emitter->unPrepare();
    }
    else if (particle->particleType == PUParticle3D::PT_TECHNIQUE)
    {
        PUParticleSystem3D* system = static_cast<PUParticleSystem3D*>(particle->particleEntityPtr);
        system->unPrepared();
    }

    particle->initForExpiration(timeElapsed);

    for (auto&& it : _listeners)
    {
        it->particleExpired(this, particle);
    }
    ///** Externs are also called to perform expiration activities. If needed, affectors and emitters may be added, but
    /// at the moment
    //	there is no reason for (and we don't want to waste cpu resources).
    //*/
    // if (!mExterns.empty())
    //{
    //	ExternIterator itExtern;
    //	ExternIterator itExternEnd = mExterns.end();
    //	for (itExtern = mExterns.begin(); itExtern != itExternEnd; ++itExtern)
    //	{
    //		(*itExtern)->_initParticleForExpiration(particle);
    //	}
    //}
}

void PUParticleSystem3D::initParticleForEmission(PUParticle3D* particle)
{
    for (auto&& it : _listeners)
    {
        it->particleEmitted(this, particle);
    }
}

void PUParticleSystem3D::addBehaviourTemplate(PUBehaviour* behaviour)
{
    auto iter = std::find(_behaviourTemplates.begin(), _behaviourTemplates.end(), behaviour);
    if (iter == _behaviourTemplates.end())
    {
        behaviour->retain();
        behaviour->_particleSystem = this;
        _behaviourTemplates.emplace_back(behaviour);
    }
}

void PUParticleSystem3D::convertToUnixStylePath(std::string& path)
{
#if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32)
    for (auto&& iter : path)
    {
        if (iter == '\\')
            iter = '/';
    }
#else
    AX_UNUSED_PARAM(path);
#endif
}

void PUParticleSystem3D::clearAllParticles()
{
    _particlePool.lockAllDatas();
    for (auto&& iter : _emittedEmitterParticlePool)
    {
        const_cast<ParticlePool&>(iter.second).lockAllDatas();
    }

    for (auto&& iter : _emittedSystemParticlePool)
    {
        const_cast<ParticlePool&>(iter.second).lockAllDatas();
    }
}

void PUParticleSystem3D::copyAttributesTo(PUParticleSystem3D* system)
{
    system->removeAllEmitter();
    system->removeAllAffector();
    system->removerAllObserver();
    system->removeAllBehaviourTemplate();
    system->removeAllListener();
    system->_particlePool.removeAllDatas();
    for (auto&& iter : system->_emittedEmitterParticlePool)
    {
        iter.second.removeAllDatas();
    }

    for (auto&& iter : system->_emittedSystemParticlePool)
    {
        iter.second.removeAllDatas();
    }

    system->setName(_name);
    system->_state = _state;
    if (_render)
        system->setRender(static_cast<PURender*>(_render)->clone());
    system->_particleQuota = _particleQuota;
    system->_blend         = _blend;
    system->_keepLocal     = _keepLocal;
    system->_isEnabled     = _isEnabled;

    for (auto&& iter : _affectors)
    {
        PUAffector* affector = static_cast<PUAffector*>(iter);
        PUAffector* copy     = PUAffectorManager::Instance()->createAffector(affector->getAffectorType());
        affector->copyAttributesTo(copy);
        system->addAffector(copy);
    }

    for (auto&& iter : _emitters)
    {
        PUEmitter* emitter = static_cast<PUEmitter*>(iter);
        PUEmitter* copy    = PUEmitterManager::Instance()->createEmitter(emitter->getEmitterType());
        emitter->copyAttributesTo(copy);
        system->addEmitter(copy);
    }

    for (auto&& iter : _observers)
    {
        PUObserver* observer = static_cast<PUObserver*>(iter);
        PUObserver* copy     = PUObserverManager::Instance()->createObserver(observer->getObserverType());
        observer->copyAttributesTo(copy);
        system->addObserver(copy);
    }

    for (auto&& iter : _behaviourTemplates)
    {
        PUBehaviour* behaviour = static_cast<PUBehaviour*>(iter);
        PUBehaviour* copy      = behaviour->clone();
        system->addBehaviourTemplate(copy);
    }

    system->_emittedEmitterQuota         = _emittedEmitterQuota;
    system->_emittedSystemQuota          = _emittedSystemQuota;
    system->_prepared                    = false;
    system->_particleSystemScaleVelocity = _particleSystemScaleVelocity;
    system->_defaultWidth                = _defaultWidth;
    system->_defaultHeight               = _defaultHeight;
    system->_defaultDepth                = _defaultDepth;
    system->_maxVelocity                 = _maxVelocity;
    system->_maxVelocitySet              = _maxVelocitySet;
    system->_matName                     = _matName;
    system->_isMarkedForEmission         = _isMarkedForEmission;
    system->_parentParticleSystem        = _parentParticleSystem;
}

PUParticleSystem3D* PUParticleSystem3D::clone()
{
    auto ps = PUParticleSystem3D::create();
    copyAttributesTo(ps);
    for (auto&& iter : _children)
    {
        PUParticleSystem3D* child = dynamic_cast<PUParticleSystem3D*>(iter);
        if (child)
            ps->addChild(child->clone());
    }
    return ps;
}

void PUParticleSystem3D::removeAllEmitter()
{
    for (auto&& iter : _emitters)
    {
        iter->release();
    }
    _emitters.clear();
}

void PUParticleSystem3D::removerAllObserver()
{
    for (auto&& iter : _observers)
    {
        iter->release();
    }
    _observers.clear();
}

void PUParticleSystem3D::removeAllBehaviourTemplate()
{
    for (auto&& iter : _behaviourTemplates)
    {
        iter->release();
    }
    _behaviourTemplates.clear();
}

void PUParticleSystem3D::removeAllListener()
{
    _listeners.clear();
}

void PUParticleSystem3D::draw(Renderer* renderer, const Mat4& transform, uint32_t flags)
{
    if (!_isEnabled)
        return;
    if (getAliveParticleCount() <= 0)
        return;
    if (_render)
        _render->render(renderer, transform, this);

    if (!_emittedSystemParticlePool.empty())
    {
        for (auto&& iter : _emittedSystemParticlePool)
        {
            PUParticle3D* particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getFirst());
            while (particle)
            {
                static_cast<PUParticleSystem3D*>(particle->particleEntityPtr)->draw(renderer, transform, flags);
                particle = static_cast<PUParticle3D*>(const_cast<ParticlePool&>(iter.second).getNext());
            }
        }
    }
}

void PUParticleSystem3D::processParticle(ParticlePool& pool,
                                         bool& firstActiveParticle,
                                         bool& firstParticle,
                                         float elapsedTime)
{
    Vec3 scale             = getDerivedScale();
    PUParticle3D* particle = static_cast<PUParticle3D*>(pool.getFirst());
    // Mat4 ltow = getNodeToWorldTransform();
    // Vec3 scl;
    // Quaternion rot;
    // ltow.decompose(&scl, &rot, nullptr);
    while (particle)
    {

        if (!isExpired(particle, elapsedTime))
        {
            particle->process(elapsedTime);

            // if (_emitter && _emitter->isEnabled())
            //     _emitter->updateEmitter(particle, elapsedTime);

            for (auto&& it : _emitters)
            {
                if (it->isEnabled() && !it->isMarkedForEmission())
                {
                    (static_cast<PUEmitter*>(it))->updateEmitter(particle, elapsedTime);
                }
            }

            for (auto&& it : _affectors)
            {
                if (it->isEnabled())
                {
                    (static_cast<PUAffector*>(it))->process(particle, elapsedTime, firstActiveParticle);
                }
            }

            if (_render)
                static_cast<PURender*>(_render)->updateRender(particle, elapsedTime, firstActiveParticle);

            if (_isEnabled && particle->particleType != PUParticle3D::PT_VISUAL)
            {
                if (particle->particleType == PUParticle3D::PT_EMITTER)
                {
                    auto emitter = static_cast<PUEmitter*>(particle->particleEntityPtr);
                    emitter->setLocalPosition(particle->position);
                    executeEmitParticles(emitter, emitter->calculateRequestedParticles(elapsedTime), elapsedTime);
                }
                else if (particle->particleType == PUParticle3D::PT_TECHNIQUE)
                {
                    auto system = static_cast<PUParticleSystem3D*>(particle->particleEntityPtr);
                    system->setPosition3D(particle->position);
                    system->setRotationQuat(particle->orientation);
                    // system->setScaleX(scl.x);system->setScaleY(scl.y);system->setScaleZ(scl.z);
                    system->forceUpdate(elapsedTime);
                }
            }

            firstActiveParticle = false;
            // Keep latest position
            particle->latestPosition = particle->position;

            // if (_maxVelocitySet && particle->calculateVelocity() > _maxVelocity)
            //{
            //     particle->direction *= (_maxVelocity / particle->direction.length());
            // }

            //// Update the position with the direction.
            // particle->position += (particle->direction * _particleSystemScaleVelocity * elapsedTime);
            // particle->positionInWorld = particle->position;
            // particle->orientationInWorld = particle->orientation;
            // particle->widthInWorld = particle->width;
            // particle->heightInWorld = particle->height;
            // particle->depthInWorld = particle->depth;

            // bool keepLocal = _keepLocal;
            // PUParticleSystem3D *parent = dynamic_cast<PUParticleSystem3D *>(getParent());
            // if (parent) keepLocal = keepLocal || parent->isKeepLocal();

            // if (keepLocal){
            //     ltow.transformPoint(particle->positionInWorld, &particle->positionInWorld);
            //     Vec3 ori;
            //     ltow.transformVector(Vec3(particle->orientation.x, particle->orientation.y, particle->orientation.z),
            //     &ori); particle->orientationInWorld.x = ori.x; particle->orientationInWorld.y = ori.y;
            //     particle->orientationInWorld.z = ori.z; particle->widthInWorld = scl.x * particle->width;
            //     particle->heightInWorld = scl.y * particle->height;
            //     particle->depthInWorld = scl.z * particle->depth;
            // }
            processMotion(particle, elapsedTime, scale, firstActiveParticle);
        }
        else
        {
            initParticleForExpiration(particle, elapsedTime);
            pool.lockLatestData();
        }

        for (auto&& it : _observers)
        {
            if (it->isEnabled())
            {
                it->updateObserver(particle, elapsedTime, firstParticle);
            }
        }

        if (particle->hasEventFlags(PUParticle3D::PEF_EXPIRED))
        {
            particle->setEventFlags(0);
            particle->addEventFlags(PUParticle3D::PEF_EXPIRED);
        }
        else
        {
            particle->setEventFlags(0);
        }

        particle->timeToLive -= elapsedTime;
        firstParticle = false;
        particle      = static_cast<PUParticle3D*>(pool.getNext());
    }
}

bool PUParticleSystem3D::makeParticleLocal(PUParticle3D* particle)
{
    if (!particle)
        return true;

    if (!_keepLocal)
        return false;

    particle->position += _latestPositionDiff;
    return true;
}

void PUParticleSystem3D::processMotion(PUParticle3D* particle,
                                       float timeElapsed,
                                       const Vec3& scl,
                                       bool /*firstParticle*/)
{
    if (particle->isFreezed())
        return;

    /** Because everything is calculated in worldspace we recalculate it back to the techniques' origin (the result is
       still worldspace) just by adding a relative offset of the particle system, technique, emitter or visual particle.
        Change in V1.4:
        The additional PEF_EMITTED check fixes the problem with scenenodese moving forward at extremely fast speeds
        The bug is due to how PU code processes keep_local, and the calculation shouldn't be applied to newly emitted
       particles.
    */
    if (!particle->hasEventFlags(PUParticle3D::PEF_EMITTED))
    {
        if (!particle->parentEmitter->makeParticleLocal(particle))
        {
            if (!makeParticleLocal(particle))
            {
                _parentParticleSystem->makeParticleLocal(particle);
            }
        }
    }

    /** Adjust position of the particle if the parent has rotated (only in case of particle system with 'keep local').
        PU 1.4: Added check that technique may not be local, otherwise particles are rotated twice as fast.
    */
    if (_parentParticleSystem->isKeepLocal() && !_keepLocal)
    {
        /** Ignore some rendersystems, because they are rotated by Ogre itself.
            Entities for example are always rotated (even if not local), based on the node orientation. A Billboard not.
        */
        if (_render && !static_cast<PURender*>(_render)->autoRotate)
        {
            _parentParticleSystem->rotationOffset(particle->position);
        }
    }

    // Added for 1.3
    if (particle->hasEventFlags(PUParticle3D::PEF_EMITTED))
        return;

    // Adjust the velocity to the allowed maximum.
    if (_maxVelocitySet && particle->calculateVelocity() > _maxVelocity)
    {
        particle->direction *= (_maxVelocity / particle->direction.length());
    }

    // Update the position with the direction.
    particle->position.add(particle->direction.x * scl.x * _particleSystemScaleVelocity * timeElapsed,
                           particle->direction.y * scl.y * _particleSystemScaleVelocity * timeElapsed,
                           particle->direction.z * scl.z * _particleSystemScaleVelocity * timeElapsed);
}

void PUParticleSystem3D::calulateRotationOffset(void)
{
    if (_isMarkedForEmission)
    {
        // Use the uber particle system as centre of rotation and not the particle systems' own position.
        _rotationCentre = _parentParticleSystem->getDerivedPosition();
    }
    else
    {
        // Use its own position
        _rotationCentre = getDerivedPosition();
    }

    /** Use the derived orientation, which is the particle systems' own scenenode orientation,
        or the orientation of the uber particle system, if this particle system is emitted itself.
    */
    Quaternion latestOrientationInverse = _latestOrientation;
    latestOrientationInverse.inverse();
    _rotationOffset = getDerivedOrientation() * latestOrientationInverse;
}

int PUParticleSystem3D::getAliveParticleCount() const
{
    int sz = 0;
    sz += _particlePool.getActiveDataList().size();

    if (!_emittedEmitterParticlePool.empty())
    {
        for (auto&& iter : _emittedEmitterParticlePool)
        {
            sz += iter.second.getActiveDataList().size();
        }
    }

    if (_emittedSystemParticlePool.empty())
        return sz;

    for (auto&& iter : _emittedSystemParticlePool)
    {
        auto pool = iter.second;
        sz += pool.getActiveDataList().size();
        PUParticle3D* particle = static_cast<PUParticle3D*>(pool.getFirst());
        while (particle)
        {
            sz += static_cast<PUParticleSystem3D*>(particle->particleEntityPtr)->getAliveParticleCount();
            particle = static_cast<PUParticle3D*>(pool.getNext());
        }
    }
    return sz;
}

void PUParticleSystem3D::forceStopParticleSystem()
{
    if (_render)
        _render->notifyStop();

    for (auto&& it : _observers)
    {
        it->notifyStop();
    }

    for (auto&& it : _emitters)
    {
        auto emitter = static_cast<PUEmitter*>(it);
        emitter->notifyStop();
    }

    for (auto&& it : _affectors)
    {
        auto affector = static_cast<PUAffector*>(it);
        affector->notifyStop();
    }
    unscheduleUpdate();
    unPrepared();
}

NS_AX_END
